package top.eggcode.system.modules.user.service;

import eu.bitwalker.useragentutils.UserAgent;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.eggcode.common.auth.SessionTokenUtil;
import top.eggcode.common.consts.ResponseStatus;
import top.eggcode.common.error.ApplicationException;
import top.eggcode.common.error.TokenRefreshException;
import top.eggcode.common.lang.EncryptionUtil;
import top.eggcode.common.mvc.IpUtil;
import top.eggcode.common.thread.AsyncExecutor;
import top.eggcode.plugins.session.Session;
import top.eggcode.plugins.session.SessionHelper;
import top.eggcode.system.modules.log.model.LoginLogEntity;
import top.eggcode.system.modules.log.service.LoginLogServiceImpl;
import top.eggcode.system.modules.user.model.AccountEntity;
import top.eggcode.system.modules.user.store.AccountRepository;
import top.eggcode.system.modules.user.store.AccountStore;
import top.eggcode.system.modules.user.tool.KeyTool;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;

/**
 * Title:
 * Description: TODO
 * Date: 2021/7/28 21:29
 *
 * @author JiaQi Ding
 * @version 1.0
 */
@RequiredArgsConstructor
@Service
public class SessionService {

    private final AccountStore accountStore;

    private final LoginLogServiceImpl loginLogService;

    private final AsyncExecutor executor;

    private final AccountRepository selfRepository;

    public Serializable create(AccountEntity account) {
        Session session = SessionHelper.getSession();

        // 标记登录
        session.setAttribute("principal", account.getUsername());

        return session.getId();
    }

    public void addLoginLog(String username, String token) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();

        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();

        String remoteAddr = IpUtil.getAddress(request);
        int remotePort = request.getRemotePort();

        // 解析客户端信息
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));

        LoginLogEntity log = new LoginLogEntity();
        log.setRemoteAddr(remoteAddr);
        log.setRemotePort(String.valueOf(remotePort));
        log.setStatus((short) 1);
        log.setBrowserName(userAgent.getBrowser().getName());
        log.setOperatingSystemName(userAgent.getOperatingSystem().getName());
        log.setDeviceType(userAgent.getOperatingSystem().getDeviceType().getName());
        log.setUsername(username);
        log.setSessionToken(token);
        try {
            executor.commit(() -> loginLogService.put(log));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public Optional<AccountEntity> login(String loginNumber, String password) {
        List<AccountEntity> accountList = selfRepository.findByUsername(loginNumber);
        if (accountList.size() == 0) {
            // 账户不存在
            throw new ApplicationException(ResponseStatus.USER_ACCOUNT_NOT_FOUND);
        }

        AccountEntity account = accountList.get(0);
        String ciphertext = KeyTool.encryptUserPassword(password, account.getSalt());
        if (account.getPassword().equals(ciphertext)) {
            return Optional.of(account);
        } else {
            // 密码错误
            throw new ApplicationException(ResponseStatus.USER_PASSWORD_ERROR);
        }
    }

    public boolean authenticate(String username, String password) {
        List<AccountEntity> accountList = selfRepository.findByUsername(username);
        if (accountList.size() == 1) {
            AccountEntity account = accountList.get(0);
            String ciphertext = KeyTool.encryptUserPassword(password, account.getSalt());
            if (account.getPassword().equals(ciphertext)) {
                return true;
            } else {
                // 密码错误
                throw new ApplicationException(ResponseStatus.USER_PASSWORD_ERROR);
            }
        }
        if (accountList.size() > 1) {
            // 账户重复
            throw new ApplicationException(ResponseStatus.SYSTEM_ACCOUNT_REPETITION);
        } else {
            // 账户不存在
            throw new ApplicationException(ResponseStatus.USER_ACCOUNT_NOT_FOUND);
        }
    }

    public boolean loginCheck(String loginNumber, String transparentPassword) {
        List<AccountEntity> accountList = selfRepository.findByUsername(loginNumber);
        if (accountList.size() == 1) {
            AccountEntity account = accountList.get(0);
            String ciphertext = EncryptionUtil.hashMd5(transparentPassword + account.getSalt());
            return account.getPassword().equals(ciphertext);
        }
        return false;
    }

    public boolean loginCheck(String token) {
        return SessionTokenUtil.verify(token);
    }

    public Optional<AccountEntity> queryByNumber(String loginNumber) {
        List<AccountEntity> accountList = selfRepository.findByUsername(loginNumber);
        if (accountList.size() == 1) {
            return Optional.of(accountList.get(0));
        }
        return Optional.empty();
    }

    public AccountEntity checkToken(String token) {

        // token 过期
        if (SessionTokenUtil.overdue(token)) {
            throw new ExpiredCredentialsException("token 过期");
        }

        // token 需要刷新
        if (SessionTokenUtil.requiredRefresh(token)) {
            throw new TokenRefreshException("token 即将过期，需要刷新");
        }

        Optional<AccountEntity> box = accountStore.getByUsername(SessionTokenUtil.getUsername(token));

        // 用户名不存在
        if (!box.isPresent()) {
            throw new UnknownAccountException("用户名不存在");
        }

        AccountEntity account = box.get();

        // 无效token
        if (!SessionTokenUtil.verify(token)) {
            throw new IncorrectCredentialsException("无效token");
        }
        return account;
    }


}
